const getType = (obj) => {
  const toString = Object.prototype.toString;
  const map = {
    '[object Boolean]': 'boolean',
    '[object Number]': 'number',
    '[object String]': 'string',
    '[object Function]': 'function',
    '[object Array]': 'array',
    '[object Date]': 'date',
    '[object RegExp]': 'regExp',
    '[object Undefined]': 'undefined',
    '[object Null]': 'null',
    '[object Object]': 'object',
    '[object Symbol]': 'symbol'
  };
  if (obj instanceof Element) {
    // 因为对不同标签，toString会返回对应不同标签的构造函数
    return 'element';
  }
  return map[toString.call(obj)];
};

const getRegExp = (re) => {
  let flags = '';
  if (re.global) flags += 'g';
  if (re.ignoreCase) flags += 'i';
  if (re.multiline) flags += 'm';
  return flags;
};

/**
 * deep clone
 * @param  {[type]} parent object 需要进行克隆的对象
 * @return {[type]}        深克隆后的对象
 */
export const deepClone = (oldObj) => {
  // 维护两个储存循环引用的数组
  const oldObjArr = [];
  const newObjArr = [];

  const clone = (oldObj) => {
    let newObj;
    let proto;

    const type = getType(oldObj);

    switch (type) {
      case 'boolean':
      case 'number':
      case 'string':
      case 'null':
      case 'undefined':
      case 'function': {
        return oldObj;
      }
      case 'symbol': {
        return Symbol(Symbol.keyFor(oldObj).toString());
      }
      case 'array': {
        newObj = [];
        break;
      }
      case 'regExp': {
        newObj = new RegExp(oldObj.source, getRegExp(oldObj));
        if (oldObj.lastIndex) newObj.lastIndex = oldObj.lastIndex;
        break;
      }
      case 'date': {
        newObj = new Date(oldObj.getTime());
        break;
      }
      // case 'obj':
      default: {
        // 处理对象原型
        proto = Object.getPrototypeOf(oldObj);
        // 利用Object.create切断原型链
        newObj = Object.create(proto);
        break;
      }
    }

    // 处理循环引用
    const index = oldObjArr.indexOf(oldObj);
    if (index !== -1) {
      // 如果父数组存在本对象,说明之前已经被引用过,直接返回此对象
      return newObjArr[index];
    }

    oldObjArr.push(oldObj);
    newObjArr.push(newObj);
    /* 数组和对象都可以用forin语句，虽然数组使用forin会有一个问题(具体看最下面）。
     * 但是这里不会影响，所以这么用
     */
    Object.keys(oldObj).forEach((i) => {
      // 递归
      newObj[i] = clone(oldObj[i]);
    });

    return newObj;
  };

  return clone(oldObj);
};
